﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using System.Reflection;

namespace System.Windows.Controls
{
    /// <summary>
    /// Implements a simple canvas control which can load Xaml resources from an assembly and align them in various ways.
    /// </summary>
    public class ResourceControl : Canvas
    {
        /// <summary>
        /// An event which fires when the <see cref="ResourceControl"/> has loaded a resource.
        /// </summary>
        public event EventHandler   DataLoaded = null;

        /// <summary>
        /// Initializes a <see cref="ResourceControl"/> instance.
        /// </summary>
        public ResourceControl()
        {
            this.Width = this.Height = 0;
        }

        private ResourcePath        _Path = new ResourcePath();
        private FrameworkElement    element = null;
        private AlignmentX          _AlignmentX = AlignmentX.Center;
        private AlignmentY          _AlignmentY = AlignmentY.Center;
        private Size                _ElementSize = new Size(0, 0);
        private string              _CachedXaml = String.Empty;
        private TransformGroup      transGroup;
        private ScaleTransform      scale;
        private RotateTransform     rotate;
        private ScaleTransform      elementScale;
        private bool                _AutoScale = false;

        private void EnsureTransform()
        {
            if (transGroup == null)
            {
                transGroup = new TransformGroup();

                scale = new ScaleTransform();
                rotate = new RotateTransform();
                transGroup.Children.Add(rotate);
                transGroup.Children.Add(scale);

                this.RenderTransform = transGroup;
            }
        }

        private void EnsureElementScale()
        {
            if (null == elementScale)
            {
                elementScale = new ScaleTransform();
            }

            if (this.element != null) this.element.RenderTransform = elementScale;
        }

        /// <summary>
        /// Specifies a <see cref="System.Windows.Size"/> to use when calculating the layout for the visual
        /// </summary>
        public Size ElementSize
        {
            get { return _ElementSize; }
            set { _ElementSize = value; }
        }

        /// <summary>
        /// Gets/Sets a boolean value indicating whether to automatically scale the visual to the dimensions of the <see cref="ResourceControl"/> itself.
        /// </summary>
        public bool AutoScale
        {
            get { return _AutoScale; }
            set { _AutoScale = value; DoLayout(); }
        }

        /// <summary>
        /// Gets/Sets a value which determines the vertical alignment of the visual element.
        /// </summary>
        public AlignmentY AlignmentY
        {
            get { return _AlignmentY; }
            set { _AlignmentY = value; DoLayout(); }
        }

        /// <summary>
        /// Gets/Sets a value which determines the horizontal alignment of the visual element.
        /// </summary>
        public AlignmentX AlignmentX
        {
            get { return _AlignmentX; }
            set { _AlignmentX = value; DoLayout(); }
        }

        /// <summary>
        /// Gets/Sets a value which determines the value of Canvas.LeftProperty for the ResourceControl
        /// </summary>
        public double X
        {
            get { return (double)GetValue(Canvas.LeftProperty); }
            set { SetValue(Canvas.LeftProperty, value); }
        }

        /// <summary>
        /// Gets/Sets a value which determines the value of Canvas.TopProperty for the ResourceControl
        /// </summary>
        public double Y
        {
            get { return (double)GetValue(Canvas.TopProperty); }
            set { SetValue(Canvas.TopProperty, value); }
        }

        /// <summary>
        /// Gets the <see cref="ResourcePath"/> which identifies the Xaml resource loaded in the control
        /// </summary>
        public ResourcePath ResourcePath
        {
            get { return _Path; }
        }

        private double ElementScale
        {
            get { EnsureTransform(); return scale.ScaleX; }
            set { EnsureTransform(); scale.ScaleX = scale.ScaleY = value; }
        }

        /// <summary>
        /// Gets the effective width of the loaded resource element
        /// </summary>
        public double ElementWidth
        {
            get 
            {
                if (element != null)
                {
                    if (element is Canvas)
                    {
                        Canvas canvas = (Canvas)element;
                        if (canvas.Width > 0) return canvas.Width;
                        if (canvas.Children[0] is Canvas)
                        {
                            return ((Canvas)canvas.Children[0]).Width;
                        }
                    }
                }

                return 0;
            }
        }

        /// <summary>
        /// Gets the effective height of the loaded resource element
        /// </summary>
        public double ElementHeight
        {
            get 
            { 
                if (element != null)
                {
                    if (element is Canvas)
                    {
                        Canvas canvas = (Canvas)element;
                        if (canvas.Height > 0) return canvas.Height;
                        if (canvas.Children[0] is Canvas)
                        {
                            return ((Canvas)canvas.Children[0]).Height;
                        }
                    }
                }

                return 0;
            }
        }

        /// <summary>
        /// Gets a <see cref="System.Windows.Size"/> which defines the actual size of the <see cref="ResourceControl"/> based on content size.
        /// </summary>
        public Size ActualSize
        {
            get
            {
                if (element != null)
                {
                    if (element is Canvas && ((Canvas)element).Children.Count > 0 &&
                        ((Canvas)element).Children[0] is FrameworkElement)
                    {
                        FrameworkElement frameElement = (FrameworkElement)((Canvas)element).Children[0];
                        return new Size(frameElement.Width, Math.Max(frameElement.Height, (Double.IsNaN(element.Height) ? 0 : element.Height)));
                    }
                    else
                    {
                        return new Size((element.Width == double.NaN ? 0 : element.Width), (Double.IsNaN(element.Height) ? 0 : element.Height));
                    }
                }
                else
                    return new Size(this.Width, this.Height);
            }
        }

        /// <summary>
        /// Returns the actual width of the <see cref="ResourceControl"/> based on content size.
        /// </summary>
        public new double ActualWidth
        {
            get { return this.ActualSize.Width; }
        }

        /// <summary>
        /// Returns the actual height of the <see cref="ResourceControl"/> based on content size.
        /// </summary>
        public new double ActualHeight
        {
            get { return this.ActualSize.Height; }
        }

        /// <summary>
        /// Find a <see cref="DependencyObject"/> by name attribute.
        /// </summary>
        /// <param name="name">The name of the element to find.</param>
        /// <returns>A <see cref="DependencyObject"/> instance or null, if no object matches the name specified</returns>
        public new DependencyObject FindName(string name)
        {
            if (element != null)
            {
                return element.FindName(name) as DependencyObject;
            }
            else
            {
                return base.FindName(name) as DependencyObject;
            }
        }

        private FrameworkElement InitializeFromXaml(string xaml)
        {
            if (element != null && this.Children.Contains(element))
            {
                this.Children.Remove(element);
                element = null;
            }

            element = System.Windows.Markup.XamlReader.Load(xaml) as FrameworkElement;

            if (element is Canvas)
            {
                Canvas outer = (Canvas)element;
                if (outer.Children.Count > 0 && outer.Children[0] is Canvas)
                {
                    Canvas inner = (Canvas)outer.Children[0];
                    outer.Width = inner.Width;
                    outer.Height = inner.Height;

                    this.Width = outer.Width;
                    this.Height = outer.Height;
                }
            }

            this.Children.Add(element);
            return element;
        }

        /// <summary>
        /// Loads raw Xaml into the <see cref="ResourceControl"/>
        /// </summary>
        /// <param name="xaml">The raw Xaml to load</param>
        public void LoadXaml(string xaml)
        {
            element = InitializeFromXaml(xaml);
            _CachedXaml = xaml;

            if (this.Width == 0) this.Width = this.ElementWidth;
            if (this.Height == 0) this.Height = this.ElementHeight;

            //Auto Size???
            double scale = 1;
            if (element != null && (this.ElementWidth > this.Width || this.ElementHeight > this.Height))
            {
                if (this.ElementWidth > this.ElementHeight)
                {
                    scale = this.Width / this.ElementWidth;
                    element.SetValue(Canvas.TopProperty, (this.Height - (this.ElementHeight * scale)) / 2);
                }
                else
                {
                    scale = this.Height / this.ElementHeight;
                    element.SetValue(Canvas.LeftProperty, (this.Width - (this.ElementWidth * scale)) / 2);
                }
            }

            this.ElementScale = scale;
            DoLayout();

            if (null != this.DataLoaded) this.DataLoaded(this, EventArgs.Empty);
        }

        /// <summary>
        /// Attempts to load raw Xaml into the <see cref="ResourceControl"/>
        /// </summary>
        /// <param name="xaml">The raw Xaml to load</param>
        /// <returns>A boolean value indicating whether the Xaml was well-formed and could be properly loaded</returns>
        public bool TryLoadXaml(string xaml)
        {
            bool result = false;

            try
            {
                element = InitializeFromXaml(xaml);
                _CachedXaml = xaml;
                result = true;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.ToString());
            }

            return result;
        }

        /// <summary>
        /// Attempts to load raw Xaml into the <see cref="ResourceControl"/> from a <see cref="System.Uri"/> address.
        /// </summary>
        /// <param name="uri">The <see cref="System.Uri"/> where the Xaml is located</param>
        /// <returns>A boolean value indicating whether the Xaml was well-formed and could be properly loaded</returns>
        public bool TryLoadXaml(Uri uri)
        {
            bool result = false;

            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
                request.BeginGetResponse(new AsyncCallback(GetXamlResponseCallback), request);
                result = true;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.ToString());
            }

            return result;
        }

        private void GetXamlResponseCallback(IAsyncResult asyncResult)
        {
            _CachedXaml = String.Empty;

            try
            {
                HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
                HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult);
                using (StreamReader responseReader = new StreamReader(response.GetResponseStream()))
                {
                    _CachedXaml = responseReader.ReadToEnd();
                }

                if (null != response) response.Close();

                Dispatcher.BeginInvoke(new Action(OnLoadXamlCallback));
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex);
            }
        }

        private void OnLoadXamlCallback()
        {
            if (!String.IsNullOrEmpty(_CachedXaml))
            {
                LoadXaml(_CachedXaml);
            }
        }

        /// <summary>
        /// Loads a Xaml construct from a <see cref="System.Uri"/> address.
        /// </summary>
        /// <param name="uri">The <see cref="System.Uri"/> from which to load the Xaml</param>
        public void LoadXaml(Uri uri)
        {
            try
            {
                TryLoadXaml(uri);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex);
            }
        }

        /// <summary>
        /// Loads a Xaml resource from an Assembly-based resource
        /// </summary>
        /// <param name="assemblyType">A <see cref="System.Type"/> which is housed in the assembly where the desired resource is located</param>
        /// <param name="resourceName">The identifier for the resource to be loaded</param>
        /// <example>
        /// ResourceControl control = new ResourceControl();
        /// control.LoadResource(MyCompany.Controls.MyControlType, "MyCompany.Resources.SomeXamlGraphic.xaml");
        /// </example>
        public void LoadResource(Type assemblyType, string resourceName)
        {
            if (element != null && this.Children.Contains(element))
            {
                this.Children.Remove(element);
                element = null;
            }

            Assembly assembly = assemblyType.Assembly;
            using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName))
            {
                System.Diagnostics.Debug.Assert(resourceStream != null, "The resource template " + resourceName + " was not found.");

                using (StreamReader sr = new StreamReader(resourceStream))
                {
                    _CachedXaml = sr.ReadToEnd();
                    element = InitializeFromXaml(_CachedXaml);

                    sr.Close();
                    sr.Dispose();
                }

                _Path.AssemblyType = assemblyType;
                _Path.AssemblyName = String.Empty;
                _Path.ResourceId = resourceName;

                resourceStream.Close();
                resourceStream.Dispose();
            }

            if (null != this.DataLoaded) this.DataLoaded(this, EventArgs.Empty);
            if (assembly != null) assembly = null;
        }

        /// <summary>
        /// Loads a Xaml resource from an Assembly-based resource
        /// </summary>
        /// <param name="path">A <see cref="ResourcePath"/> which identifies the resource to load</param>
        /// <example>
        /// ResourceControl control = new ResourceControl();
        /// ResourcePath path = new ResourcePath(MyCompany.Controls.MyControlType, "MyCompany.Resources.SomeXamlGraphic.xaml");
        /// control.LoadResource(path);
        /// </example>
        public void LoadResource(ResourcePath path)
        {
            _Path = path;
            if (path != null)
            {
                if (path.AssemblyType != null)
                    LoadResource(path.AssemblyType, path.ResourceId);
                else
                    LoadResource(path.AssemblyName, path.ResourceId);
            }
        }

        /// <summary>
        /// Attempts to load a Xaml resource from an Assembly-based resource
        /// </summary>
        /// <param name="path">A <see cref="ResourcePath"/> which identifies the resource to load</param>
        /// <example>
        /// ResourceControl control = new ResourceControl();
        /// ResourcePath path = new ResourcePath(MyCompany.Controls.MyControlType, "MyCompany.Resources.SomeXamlGraphic.xaml");
        /// if (!control.LoadResource(path))
        /// {
        ///     //Do Something
        /// }
        /// </example>
        /// <returns>A boolean value indicating whether the resource was loaded successfully</returns>
        public bool TryLoadResource(ResourcePath path)
        {
            bool result = false;

            try
            {
                LoadResource(path);
                result = true;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.ToString());
            }

            return result;
        }

        /// <summary>
        /// Loads a Xaml resource from an Assembly-based resource
        /// </summary>
        /// <param name="assemblyName">The short name for the assembly - must be available in the AppDomain</param>
        /// <param name="resourceName">The name of the resource, including the namespace</param>
        public void LoadResource(string assemblyName, string resourceName)
        {
            if (!assemblyName.Contains("Version=")) assemblyName += ",Version=1.0.0.0,Culture=neutral,PublicKeyToken=null";

            if (element != null && this.Children.Contains(element))
            {
                this.Children.Remove(element);
                element = null;
            }

            Assembly assembly = Assembly.Load(assemblyName);
            System.Diagnostics.Debug.Assert(assembly != null, "The resource assembly " + assemblyName + " was not found.");

            using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName))
            {
                System.Diagnostics.Debug.Assert(resourceStream != null, "The resource template " + resourceName + " was not found.");

                using (StreamReader sr = new StreamReader(resourceStream))
                {
                    _CachedXaml = sr.ReadToEnd();
                    element = InitializeFromXaml(_CachedXaml);
                }

                _Path.AssemblyName = assemblyName;
                _Path.AssemblyType = null;
                _Path.ResourceId = resourceName;

                resourceStream.Close();
            }

            if (null != this.DataLoaded) this.DataLoaded(this, EventArgs.Empty);
            if (assembly != null) assembly = null;
        }

        /// <summary>
        /// Attempts to load a Xaml resource from an Assembly-based resource
        /// </summary>
        /// <param name="assemblyName">The short name for the assembly - must be available in the AppDomain</param>
        /// <param name="resourceName">The name of the resource, including the namespace</param>
        /// <returns>A boolean value indicating whether the resource could be loaded or not</returns>
        public bool TryLoadResource(string assemblyName, string resourceName)
        {
            bool result = false;

            try
            {
                LoadResource(assemblyName, resourceName);
                _Path.AssemblyName = assemblyName;
                _Path.ResourceId = resourceName;

                result = true;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.ToString());
            }

            return result;
        }

        /// <summary>
        /// Attempts to load a Xaml resource from an Assembly-based resource
        /// </summary>
        /// <param name="assemblyType">A <see cref="System.Type"/> which is housed in the assembly where the desired resource is located</param>
        /// <param name="resourceName">The name of the resource, including the namespace</param>
        /// <returns>A boolean value indicating whether the resource could be loaded or not</returns>
        public bool TryLoadResource(Type assemblyType, string resourceName)
        {
            bool result = false;

            try
            {
                LoadResource(assemblyType, resourceName);
                _Path.AssemblyType = assemblyType;
                _Path.ResourceId = resourceName;

                result = true;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.ToString());
            }

            return result;
        }

        private void DoLayout()
        {
            if (element != null)
            {
                Rect rect = new Rect(0, 0, this.Width, this.Height);

                if (_ElementSize.Width > 0 || element.Width > 0)
                {
                    if (!this.AutoScale)
                    {
                        Size size = new Size((_ElementSize.Width > 0 ? _ElementSize.Width : element.Width), (_ElementSize.Height > 0 ? _ElementSize.Height : element.Height));
                        LayoutHelper.AlignVisualBounds(element, size, rect, _AlignmentX, _AlignmentY);
                    }
                    else
                    {
                        Size size = new Size(element.Width, element.Height);

                        EnsureElementScale();
                        if (elementScale != null)
                        {
                            double scaleX = this.Width / size.Width;
                            double scaleY = this.Height / size.Height;
                            elementScale.ScaleX = scaleX;
                            elementScale.ScaleY = scaleY;
                            LayoutHelper.SetVisualLocation(element, 0, 0);
                        }
                    }
                }
                else
                {
                    if (this.ElementWidth > this.Width || this.ElementHeight > this.Height &&
                        (this.Width > 0 && this.Height > 0))
                    {
                        if (this.ElementWidth > this.Width)
                        {
                            element.SetValue(Canvas.TopProperty, ((this.Height - (this.ElementHeight * this.ElementScale)) / 2));
                            element.SetValue(Canvas.LeftProperty, 0.0);
                        }
                        else
                        {
                            element.SetValue(Canvas.LeftProperty, ((this.Width - (this.ElementWidth * this.ElementScale)) / 2));
                            element.SetValue(Canvas.TopProperty, 0.0);
                        }
                    }
                    else
                    {
                        LayoutHelper.AlignVisualBounds(element, this.ActualSize, rect, _AlignmentX, _AlignmentY);
                    }
                }
            }
        }
    }
}
